package net.miranti.client;

import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import net.miranti.client.events.FailureEvent;
import net.miranti.client.events.Handler;
import net.miranti.client.events.UpdateContentPanelEvent;

import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.GWT.UncaughtExceptionHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.HandlerManager;
import com.google.gwt.user.client.History;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.DialogBox;
import com.google.gwt.user.client.ui.HTMLPanel;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.ScrollPanel;

public abstract class AbstractPlaceManager implements ValueChangeHandler<String> {
	protected final RootPanel messagesPanel = RootPanel.get("messages");
	protected final RootPanel contentPanel = RootPanel.get("content");

	protected final HandlerManager eventBus;
	
	public abstract void go(String token);

	public AbstractPlaceManager(HandlerManager eventBus) {
		this.eventBus = eventBus;
		History.addValueChangeHandler(this);

		if (!GWT.isScript()) {
			GWT.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
				@Override
				public void onUncaughtException(Throwable e) {
					GWT.log("Exception: ", e);
					Window.alert(e.getMessage());
				}
			});
		}
		eventBus.addHandler(FailureEvent.TYPE, new Handler<FailureEvent>() {
			@Override
			public void onEvent(FailureEvent e) {
				DialogBox d = new DialogBox();
				d.setGlassEnabled(true);
				d.setAutoHideEnabled(true);
				d.setAnimationEnabled(true);
				d.setWidget(new ScrollPanel(new HTMLPanel(e.error.getMessage())));
				d.center();
				d.show();
			}
		});
		eventBus.addHandler(UpdateContentPanelEvent.TYPE, new Handler<UpdateContentPanelEvent>() {
			@Override
			public void onEvent(UpdateContentPanelEvent e) {
				contentPanel.clear();
				contentPanel.add(e.widget);
			}
		});
	}
	
	// in reaction to app Events, don't fire ValueChangeEvent (just updates URL)
	protected void updateHistory(String token) {
		if (!token.equals(History.getToken()))
			History.newItem(token, false);
	}

	@Override
	public void onValueChange(ValueChangeEvent<String> event) {
		go(event.getValue());
	}
	
	protected class TokenBuilder {
		private final String token;
		private final Map<String, List<String>> parameters = new LinkedHashMap();

		public TokenBuilder() {
			this.token = "";
		}

		public TokenBuilder(String token) {
			this.token = token;
		}

		public TokenBuilder addParameter(String key, Number... values) {
			int n = values.length;
			String[] aux = new String[n];
			for (int i=0; i<n; i++)
				aux[i] = values[i].toString();
			return addParameter(key, aux);
		}

		public TokenBuilder addParameter(String key, Date... values) {
			int n = values.length;
			String[] aux = new String[n];
			for (int i=0; i<n; i++)
				aux[i] = String.valueOf(values[i].getTime());
			return addParameter(key, aux);
		}

		public TokenBuilder addParameter(String key, String... values) {
			List<String> valuesList = parameters.get(key);
			if (valuesList==null) {
				valuesList = new LinkedList();
				parameters.put(key, valuesList);
			}
			valuesList.addAll(Arrays.asList(values));
			return this;
		}
		
		public Map<String, List<String>> getParametersMap() {
			Map aux = new LinkedHashMap();
			for(Entry<String, List<String>> entry : parameters.entrySet())
				aux.put(entry.getKey(), Collections.unmodifiableList(entry.getValue()));
			return Collections.unmodifiableMap(aux);
		}
		
		public String generateToken() {
			StringBuilder sb = new StringBuilder(token);
			for(Entry<String, List<String>> entry : parameters.entrySet()) {
				String key = entry.getKey();
				List<String> values = entry.getValue();
				for (String v : values)
					sb.append("&").append(key).append("=").append(v);
			}
			return sb.toString();
		}

		@Override
		public String toString() {
			return generateToken();
		}
	}
	
	protected class TokenParser {
		public final String key;
		public final Map<String, LinkedList<String>> parametersMap;

		public TokenParser(String tokenString) {
			String[] split = tokenString.split("&");
			key = split[0];
			
			HashMap<String, LinkedList<String>> params = new HashMap();
			for (int i=1, n=split.length; i<n; i++) {
				String[] key_value = split[i].split("=");
				String key = key_value[0];
				LinkedList<String> values = params.get(key);
				if (values==null) {
					values = new LinkedList<String>();
					params.put(key, values);
				}
				values.add(key_value.length==1 ? "" : key_value[1]);
			}
			parametersMap = Collections.unmodifiableMap(params);
		}
		
		public String getParameter(String name) {
			LinkedList<String> values = parametersMap.get(name);
			return values!=null && values.size()>0 ? values.get(0) : null;
		}

		public Integer getInteger(String name) {
			String v = getParameter(name);
			return v!=null && !v.isEmpty() ? new Integer(v) : null;
		}

		public Long getLong(String name) {
			String v = getParameter(name);
			return v!=null && !v.isEmpty() ? new Long(v) : null;
		}

		public Date getDate(String name) {
			String v = getParameter(name);
			return v!=null && !v.isEmpty() ? new Date(new Long(v)) : null;
		}

		public List<String> getParameterValues(String name) {
			return parametersMap.get(name);
		}
		
		public Long[] getLongArrayValues(String name) {
			List<String> values = parametersMap.get(name);
			int n = values.size();
			Long[] res = new Long[n];
			for (int i=0; i<n; i++) {
				String v = values.get(i);
				res[i] = v.isEmpty() ? null : new Long(v);
			}
			return res;
		}
	}
}